﻿//
// CloneFactory - Copyright Henning Nybro Johnsrud
//

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;

namespace CodeDeeds.Reflection.Cloning
{

   /// <summary>
   /// Helper class for cloning any object.
   /// </summary>
   public static class CloneFactory
   {
      /// <summary>
      /// Makes a shallow clone of an object.
      /// </summary>
      /// <param name="obj">The object to clone.</param>
      /// <returns>A clone of the object.</returns>
      public static object MakeShallowClone(this object obj)
      {
         return CloneFactoryHelper.MakeShallowClone(obj);
      }

      /// <summary>
      /// Makes a shallow clone of an object.
      /// </summary>
      /// <typeparam name="T">The type of the object to clone.</typeparam>
      /// <param name="obj">The object to clone.</param>
      /// <returns>A clone of the object.</returns>
      public static T MakeShallowClone<T>(this T obj)
      {
         return CloneFactoryHelper<T>.MemberwiseShallowCloneDelegate(obj);
      }


      /// <summary>
      /// Makes a deep clone of an object.
      /// </summary>
      /// <param name="obj">The object to clone.</param>
      /// <returns>A clone of the object.</returns>
      public static object MakeDeepClone(this object obj)
      {
         return CloneFactoryHelper.MakeDeepClone(obj);
      }

      /// <summary>
      /// Makes a deep clone of an object.
      /// </summary>
      /// <typeparam name="T">The type of the object to clone.</typeparam>
      /// <param name="obj">The object to clone.</param>
      /// <returns>A clone of the object.</returns>
      public static T MakeDeepClone<T>(this T obj)
      {
         return CloneFactoryHelper<T>.DeepCloneTypedObjectInstance(obj);
      }

      /// <summary>
      /// Set the CloneTypeBehaviour of the type T.
      /// </summary>
      /// <typeparam name="T">The type to set the CloneTypeBehaviour on.</typeparam>
      /// <param name="behaviour">The CloneTypeBehaviour.</param>
      public static void SetTypeCloneBehaviour<T>(CloneTypeBehaviour behaviour)
      {
         CloneTypeMetadata<T>.SetTypeCloneBehaviour(behaviour);
      }

      /// <summary>
      /// Set the CloneFieldBehaviour of a spesific field on the type of T.
      /// </summary>
      /// <typeparam name="T">The type which owns the field to set the CloneFieldBehaviour on.</typeparam>
      /// <param name="field">The field to set the clone behaviour on.</param>
      /// <param name="behaviour">The CloneFieldBehaviour.</param>
      public static void SetFieldCloneBehaviour<T>(FieldInfo field, CloneFieldBehaviour behaviour)
      {
         CloneTypeMetadata<T>.SetFieldCloneBehaviour(field, behaviour);
      }

      /// <summary>
      /// Gets the number of types there is generated CloneFactories for.
      /// </summary>
      public static int TypeCount
      {
         get { return CloneFactoryHelper.s_typeCount; }
      }



      /// <summary>
      /// Helper class for calling the correct cloning routines for a specified type.
      /// </summary>
      private class CloneFactoryHelper
      {

         internal delegate object DeepCloneObjectDelegate(object obj, CloningContext context);

         /// <summary>
         /// Makes a shallow clone of an object.
         /// </summary>
         /// <param name="obj">The object to clone.</param>
         /// <returns>A clone of the object.</returns>
         public static object MakeShallowClone(object obj)
         {
            if (null == obj)
            {
               return null;
            }
            Func<object, object> cloneDelegate = GetShallowCloneDelegate(obj.GetType());
            return cloneDelegate(obj);
         }

         /// <summary>
         /// Makes a deep clone of an object.
         /// </summary>
         /// <param name="obj">The object to clone.</param>
         /// <returns>A clone of the object.</returns>
         public static object MakeDeepClone(object obj)
         {
            if (null == obj)
            {
               return null;
            }
            DeepCloneObjectDelegate cloneDelegate = GetDeepCloneDelegate(obj.GetType());
            return cloneDelegate(obj, new CloningContext());
         }

         /// <summary>
         /// Makes a deep clone of an object.
         /// </summary>
         /// <param name="obj">The object to clone.</param>
         /// <param name="context">Existing cloned object references.</param>
         /// <returns>A clone of the object.</returns>
         public static object MakeDeepClone(object obj, CloningContext context)
         {
            if (null == obj)
            {
               return null;
            }
            DeepCloneObjectDelegate cloneDelegate = GetDeepCloneDelegate(obj.GetType());
            return cloneDelegate(obj, context);
         }

         /// <summary>
         /// Gets a shallow cloning delegate for the specified type.
         /// </summary>
         /// <param name="objectType">The type to get a cloning delegate for.</param>
         /// <returns>A cloning delegate.</returns>
         private static Func<object, object> GetShallowCloneDelegate(Type objectType)
         {
            return s_knownObjectTypeShallowCloneDelegates.GetOrAdd(objectType, (Type t) =>
            {
               return (Func<object, object>)typeof(CloneFactoryHelper<>).MakeGenericType(t).GetField("ObjectMemberwiseShallowCloneDelegate", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).GetValue(null);
            });
         }

         // A dictionary of object shallow clone delegates for all types which have already been cloned once.
         private static readonly ConcurrentDictionary<Type, Func<object, object>> s_knownObjectTypeShallowCloneDelegates = new ConcurrentDictionary<Type, Func<object, object>>();

         /// <summary>
         /// Gets a cloning delegate for the specified type.
         /// </summary>
         /// <param name="objectType">The type to get a cloning delegate for.</param>
         /// <returns>A cloning delegate.</returns>
         private static DeepCloneObjectDelegate GetDeepCloneDelegate(Type objectType)
         {
            return s_knownObjectTypeDeepCloneDelegates.GetOrAdd(objectType, (Type t) =>
            {
               return (DeepCloneObjectDelegate)typeof(CloneFactoryHelper<>).MakeGenericType(t).GetField("ObjectMemberwiseDeepCloneDelegate", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).GetValue(null);
            });
         }

         // A dictionary of object deep clone delegates for all types which have already been cloned once.
         private static readonly ConcurrentDictionary<Type, DeepCloneObjectDelegate> s_knownObjectTypeDeepCloneDelegates = new ConcurrentDictionary<Type, DeepCloneObjectDelegate>();

         // Used for giving each type a unique index number
         internal static int ObjectTypeIndexCounter = 1; // Start at 1. 0 is reserved as common type index for all non-class types, which does not have heap instances in this context.


         public static int s_typeCount = 0;


         internal static void InPlaceCloneArrayMembers(Array array, CloningContext context)
         {
            int[] dimIndexes = new int[array.Rank];
            int dim = 0;
            InPlaceCloneArrayDimMembers(array, dim, dimIndexes, context);
         }

         private static void InPlaceCloneArrayDimMembers(Array array, int dim, int[] dimIndexes, CloningContext context)
         {
            bool isLastDim = (dim == array.Rank - 1);
            for(int i = array.GetLowerBound(dim); i <= array.GetUpperBound(dim); ++i)
            {
               dimIndexes[dim] = i;
               if(isLastDim)
               {
                  array.SetValue(MakeDeepClone(array.GetValue(dimIndexes)), dimIndexes);
               }
               else
               {
                  InPlaceCloneArrayDimMembers(array, dim + 1, dimIndexes, context);
               }
            }
         }
      }


      /// <summary>
      /// The generic implementation of the clone factory.
      /// </summary>
      /// <typeparam name="T">The type of object to clone.</typeparam>
      private class CloneFactoryHelper<T> : CloneFactoryHelper
      {
         /// <summary>
         /// Delegate type for deep cloning objects of type T.
         /// </summary>
         /// <param name="obj">The object to clone.</param>
         /// <param name="context">The cloning context.</param>
         /// <returns>A clone of the object.</returns>
         internal delegate T DeepCloneTDelegate(T obj, CloningContext context);


         // Store a runtime type definition of the clone object for reflection.
         public static readonly Type ObjectType;

         // A unique type index for this type.
         public static readonly int TypeIndex;

         // A flag indicating if the object type is considdered immutable and therefore will not be cloned.
         public static readonly bool ObjectTypeIsImmutable;

         /// <summary>
         /// A specialized shallow clone deligate for the type T, which is created in the static constuctor.
         /// </summary>
         internal static readonly Func<T, T> MemberwiseShallowCloneDelegate;

         /// <summary>
         /// A specialized shallow clone deligate for the type T, which is taking an object and returns an object.
         /// </summary>
         internal static readonly Func<object, object> ObjectMemberwiseShallowCloneDelegate;

         /// <summary>
         /// A specialized clone deligate for the type T, which is created in the static constuctor.
         /// </summary>
         internal static readonly DeepCloneTDelegate MemberwiseDeepCloneDelegate;

         /// <summary>
         /// A specialized clone deligate for the type T, which is taking an object and returns an object.
         /// </summary>
         internal static readonly DeepCloneObjectDelegate ObjectMemberwiseDeepCloneDelegate;

         /// <summary>
         /// Static constuctor building the clone delegates for the type T at first time use.
         /// </summary>
         static CloneFactoryHelper()
         {
            ++s_typeCount;

            ObjectType = typeof(T);
            ObjectTypeIsImmutable = (CloneTypeMetadata<T>.GetClassBehaviour() == CloneTypeBehaviour.KeepOriginal);
            if (!ObjectTypeIsImmutable)
            {
               TypeIndex = System.Threading.Interlocked.Increment(ref ObjectTypeIndexCounter) - 1;
            }

            if (ObjectTypeIsImmutable)
            {
               MemberwiseDeepCloneDelegate = CreateILNoCloneDelegate();
            }
            else if (ObjectType.IsArray)
            {
               MemberwiseDeepCloneDelegate = CreateILArrayCloneDelegate();
            }
            else if (ObjectType.IsClass || ObjectType.IsValueType)
            {
               MemberwiseDeepCloneDelegate = CreateILDeepCloneDelegate();
            }
            else
            {
               // Need to get the type from the object instance to call the right clone helper...
               MemberwiseDeepCloneDelegate = CreateILUnknownObjectCloneDelegate();
            }

            ObjectMemberwiseDeepCloneDelegate = (object obj, CloningContext context) => (object)MemberwiseDeepCloneDelegate((T)obj, context);


            //---------------------------------------------------------------
            // Create the shallow copy delegates
            if (ObjectTypeIsImmutable)
            {
               // Classes or structs considered immutable are not cloned, but the same value is returned.
               MemberwiseShallowCloneDelegate = x => x;
               ObjectMemberwiseShallowCloneDelegate = x => x;
            }
            else if (ObjectType.IsArray)
            {
               MemberwiseShallowCloneDelegate = x => (T)((Array)(object)x).Clone();
               ObjectMemberwiseShallowCloneDelegate = x => ((Array)x).Clone();
            }
            else if (ObjectType.IsValueType)
            {
               MemberwiseShallowCloneDelegate = x => x; // Just simple shallow struct copy
               ObjectMemberwiseShallowCloneDelegate = x => (T)x; // Unbox and box again
            }
            else if (ObjectType.IsClass)
            {
               var mclone = (Func<T, object>)Delegate.CreateDelegate(typeof(Func<T, object>), CloneFactoryCodeGenHelper.s_objectMemberwiseClone);
               MemberwiseShallowCloneDelegate = (T inst) => (T)mclone(inst);
               ObjectMemberwiseShallowCloneDelegate = (object inst) => mclone((T)inst);
            }
            else
            {
               // Need to get the type from the object instance to call the right clone helper...
               MemberwiseShallowCloneDelegate = (T inst) => (T)CloneFactoryHelper.MakeShallowClone((object)inst);
               ObjectMemberwiseShallowCloneDelegate = (object inst) => CloneFactoryHelper.MakeShallowClone(inst);
            }
         }


         /// <summary>
         /// Clones an object instace.
         /// </summary>
         /// <param name="obj">The object to clone.</param>
         /// <returns>The cloned object</returns>
         public static object DeepCloneObjectInstance(object obj)
         {
            return MemberwiseDeepCloneDelegate((T)obj, new CloningContext());
         }

         /// <summary>
         /// Clones an object instace.
         /// </summary>
         /// <param name="obj">The object to clone.</param>
         /// <param name="context">Existing cloned object references.</param>
         /// <returns>The cloned object</returns>
         public static object DeepCloneObjectInstance(object obj, CloningContext context)
         {
            return MemberwiseDeepCloneDelegate((T)obj, context);
         }

         /// <summary>
         /// Clones a typed object instace.
         /// </summary>
         /// <param name="obj">The object to clone.</param>
         /// <returns>The cloned object</returns>
         public static T DeepCloneTypedObjectInstance(T obj)
         {
            return MemberwiseDeepCloneDelegate(obj, new CloningContext());
         }

         /// <summary>
         /// Clones a typed object instace.
         /// </summary>
         /// <param name="obj">The object to clone.</param>
         /// <param name="context">Existing cloned object references.</param>
         /// <returns>The cloned object</returns>
         public static T DeepCloneTypedObjectInstance(T obj, CloningContext context)
         {
            return MemberwiseDeepCloneDelegate(obj, context);
         }


         internal static DynamicMethod DeepCloneDynamicMethod;


         private static DeepCloneTDelegate CreateILDeepCloneDelegate()
         {
            DeepCloneDynamicMethod = CloneFactoryCodeGenHelper.CreateILDeepCloneMethod(new CloneTypeMetadata<T>());
            return (DeepCloneTDelegate)DeepCloneDynamicMethod.CreateDelegate(typeof(DeepCloneTDelegate));
         }

         private static DeepCloneTDelegate CreateILNoCloneDelegate()
         {
            DeepCloneDynamicMethod =
               new DynamicMethod(ObjectType.FullName + "_ILNoClone",
                                 ObjectType, // Returns cloned object of type T.
                                 new Type[] { ObjectType, // Object to clone 
                                              typeof(CloningContext) }, // Existing clones dictionary
                                 ObjectType,
                                 true);
            ILGenerator ilGen = DeepCloneDynamicMethod.GetILGenerator(8);

            // Just return the original instance
            ilGen.Emit(OpCodes.Ldarg_0);
            ilGen.Emit(OpCodes.Ret);

            return (DeepCloneTDelegate)DeepCloneDynamicMethod.CreateDelegate(typeof(DeepCloneTDelegate));
         }

         private static DeepCloneTDelegate CreateILUnknownObjectCloneDelegate()
         {
            DeepCloneDynamicMethod = CloneFactoryCodeGenHelper.CreateILUnknownObjectCloneMethod(new CloneTypeMetadata<T>());
            return (DeepCloneTDelegate)DeepCloneDynamicMethod.CreateDelegate(typeof(DeepCloneTDelegate));
         }

         private static DeepCloneTDelegate CreateILArrayCloneDelegate()
         {
            DeepCloneDynamicMethod = CloneFactoryCodeGenHelper.CreateILArrayCloneMethod(new CloneTypeMetadata<T>());
            return (DeepCloneTDelegate)DeepCloneDynamicMethod.CreateDelegate(typeof(DeepCloneTDelegate));
         }
      }


      private static class CloneFactoryCodeGenHelper
      {
         internal static readonly MethodInfo s_miDeepCloneObject = typeof(CloneFactoryHelper).GetMethod("MakeDeepClone", new Type[] { typeof(object), typeof(CloningContext) } );
         internal static readonly MethodInfo s_miInPlaceCloneArrayMembers = typeof(CloneFactoryHelper).GetMethod("InPlaceCloneArrayMembers", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
         internal static readonly MethodInfo s_cloningcontextGetExistingClone = typeof(CloningContext).GetMethod("GetExistingClone", BindingFlags.Public | BindingFlags.Instance);
         internal static readonly MethodInfo s_cloningcontextAddNewClone = typeof(CloningContext).GetMethod("AddNewClone", BindingFlags.Public | BindingFlags.Instance);
         internal static readonly MethodInfo s_objectGetType = typeof(object).GetMethod("GetType", BindingFlags.Instance | BindingFlags.Public);
         internal static readonly MethodInfo s_objectReferenceEquals = typeof(object).GetMethod("ReferenceEquals", BindingFlags.Static | BindingFlags.Public);
         internal static readonly MethodInfo s_objectMemberwiseClone = typeof(object).GetMethod("MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
         internal static readonly MethodInfo s_arrayClone = typeof(Array).GetMethod("Clone", BindingFlags.Instance | BindingFlags.Public);
         internal static readonly MethodInfo s_typeGetTypeFromHandle = typeof(Type).GetMethod("GetTypeFromHandle");

         internal static DynamicMethod CreateILDeepCloneMethod(CloneTypeMetadata metadata)
         {
            DynamicMethod deepCloneDynamicMethod =
               new DynamicMethod(metadata.ObjectType.FullName + "_ILDeepClone",
                                 metadata.ObjectType, // Returns cloned object of type T.
                                 new Type[] { metadata.ObjectType, // Object to clone 
                                              typeof(CloningContext) }, // Existing clones dictionary
                                 metadata.ObjectType, // Act as a member of the type beeing cloned
                                 true);

            var fields = metadata.FieldBehaviours.Where(kv => kv.Value != CloneFieldBehaviour.KeepOriginal);

            ILGenerator ilGen = deepCloneDynamicMethod.GetILGenerator(fields.Count() * 25 + 100);

            // Local variable to store the clone in
            var loc_clone = ilGen.DeclareLocal(metadata.ObjectType);
            var loc_originalRef = ilGen.DeclareLocal(metadata.ObjectType.IsValueType ? metadata.ObjectType.MakeByRefType() : metadata.ObjectType);
            var loc_cloneRef = ilGen.DeclareLocal(metadata.ObjectType.IsValueType ? metadata.ObjectType.MakeByRefType() : metadata.ObjectType);

            if (metadata.ObjectType.IsValueType)
            {
               if (metadata.ClassBehaviour == CloneTypeBehaviour.ShallowClone)
               {
                  // Just copy by value.
                  ilGen.Emit(OpCodes.Ldarg_0); // Shallow copy the original
                  ilGen.Emit(OpCodes.Ret);     // and return.
                  return deepCloneDynamicMethod;
               }
               else
               {
                  // Just copy by value and clone reference type members.
                  ilGen.Emit(OpCodes.Ldarg_0); // Shallow copy the original
                  ilGen.Emit(OpCodes.Stloc_0); // and store it.
               }
            }
            else // Clone a class instance
            {

               if (metadata.ClassBehaviour == CloneTypeBehaviour.KeepOriginal)
               {
                  // Just copy the reference.
                  ilGen.Emit(OpCodes.Ldarg_0); // Keep reference to the original
                  ilGen.Emit(OpCodes.Ret);     // and return.
                  return deepCloneDynamicMethod;
               }

               var lblNullTest1 = ilGen.DefineLabel();
               var lblNullTest2 = ilGen.DefineLabel();
               var lblCorrectType = ilGen.DefineLabel();

               //----------------------------------------------------------------------
               // Test for a null value
               ilGen.Emit(OpCodes.Ldarg_0); // Put the original on the stack.
               ilGen.Emit(OpCodes.Brtrue_S, lblNullTest1); // Check if null...
               // The value was null.
               ilGen.Emit(OpCodes.Ldnull); // Put null on the stack
               ilGen.Emit(OpCodes.Ret);    // and return.

               // We have a non null value
               ilGen.MarkLabel(lblNullTest1);

               if (!metadata.ObjectType.IsSealed) // If the class is not sealed, we need to check if the instance is a derived class of T.
               {
                  //-------------------------------------------------------------------------------
                  // Check if we have the exact type or a derived class type
                  ilGen.Emit(OpCodes.Ldtoken, metadata.ObjectType);
                  ilGen.Emit(OpCodes.Call, s_typeGetTypeFromHandle);
                  ilGen.Emit(OpCodes.Ldarg_0);
                  ilGen.Emit(OpCodes.Call, s_objectGetType); // Get the actual type of the object instance
                  ilGen.Emit(OpCodes.Call, s_objectReferenceEquals); // Should be the same type instace if equal type.
                  ilGen.Emit(OpCodes.Brtrue_S, lblCorrectType); // Is it the exact type? If so jump to lblCorrectType.

                  // Need to clone the actual object type and not the base class type.
                  ilGen.Emit(OpCodes.Ldarg_0);  // Put the original on the stack.
                  ilGen.Emit(OpCodes.Ldarg_1);  // Put the context on the stack
                  ilGen.Emit(OpCodes.Call, s_miDeepCloneObject);  // Call the clone method for any object value
                  ilGen.Emit(OpCodes.Ret);
               }

               ilGen.MarkLabel(lblCorrectType);

               //-------------------------------------------------------------------------------
               // Important to only clone each object instance once to handle circle references correctly.
               // Check if we already have a clone of the object instance...
               ilGen.Emit(OpCodes.Ldarg_1);   // Put the clone context on the stack
               ilGen.Emit(OpCodes.Ldc_I4, metadata.TypeIndex);  // Put the type index on the stack.
               ilGen.Emit(OpCodes.Ldarg_0);   // Put the original object on the stack.
               ilGen.Emit(OpCodes.Call, s_cloningcontextGetExistingClone); // Do we already have an existing clone of the object instance?
               ilGen.Emit(OpCodes.Stloc_0);   // Save clone
               ilGen.Emit(OpCodes.Ldloc_0);   // Put the clone back on the stack.
               ilGen.Emit(OpCodes.Brfalse_S, lblNullTest2); // Do we already have a clone of the object?
               // We have an existing clone. 
               ilGen.Emit(OpCodes.Ldloc_0);   // Put it back on the stack
               ilGen.Emit(OpCodes.Ret);       // and return.

               // The object has not been cloned yet...
               ilGen.MarkLabel(lblNullTest2);


               // Make a new clone...
               ilGen.Emit(OpCodes.Ldarg_0); // Put the original object on the stack.
               ilGen.Emit(OpCodes.Call, s_objectMemberwiseClone); // Call MemberwiseClone
               // Now we have a shallow copy on the stack. Is that good enough?
               if (metadata.ClassBehaviour == CloneTypeBehaviour.ShallowClone)
               {
                  // Only shallow copy, which was provided by MemberviseClone i required.
                  ilGen.Emit(OpCodes.Ret);     // Return the shallow copy.
                  return deepCloneDynamicMethod;
               }
               ilGen.Emit(OpCodes.Stloc_0); // Save the new clone

               // Save the new clone in the dictionary of the clone context
               ilGen.Emit(OpCodes.Ldarg_1);  // Put the clone context on the stack
               ilGen.Emit(OpCodes.Ldc_I4, metadata.TypeIndex); // Put the type index on the stack.
               ilGen.Emit(OpCodes.Ldarg_0);  // Put the original object on the stack.
               ilGen.Emit(OpCodes.Ldloc_0);  // Put the clone on the stack.
               ilGen.Emit(OpCodes.Call, s_cloningcontextAddNewClone);  // Call ObjectCloneContext.AddNewClone(...)
            }

            //-------------------------------------------
            // Clone the value of all mutable members
            if (metadata.ObjectType.IsValueType)
            {
               ilGen.Emit(OpCodes.Ldarga_S, (byte)0); // Put the address reference of the original on the stack.
               ilGen.Emit(OpCodes.Ldloca_S, (byte)0); // Put the address of the clone on the stack.
            }
            else
            {
               ilGen.Emit(OpCodes.Ldarg_0); // Put the original object reference on the stack.
               ilGen.Emit(OpCodes.Ldloc_0); // Put the clone on the stack.
            }
            ilGen.Emit(OpCodes.Stloc_2); // Put the address reference of the clone in local variable 2.
            ilGen.Emit(OpCodes.Stloc_1); // Put the address reference of the original in local variable 1.
            EmitMemberFieldsCloneCode(ilGen, deepCloneDynamicMethod, fields, metadata);

            ilGen.Emit(OpCodes.Ldloc_0); // Put the clone on the stack
            ilGen.Emit(OpCodes.Ret);     // and return.

            return deepCloneDynamicMethod;
         }


         private static void EmitMemberFieldsCloneCode(ILGenerator ilGen, DynamicMethod deepCloneDynamicMethod, IEnumerable<KeyValuePair<FieldInfo, CloneFieldBehaviour>> fields, CloneTypeMetadata metadata)
         {
            // A reference to the original is expected in local variable 1
            // A reference to a new shallow copy of the original is expected in local variable 2
            foreach (var fi in fields)
            {
               Type propCloneType = typeof(CloneFactoryHelper<>).MakeGenericType(fi.Key.FieldType);

               if (fi.Value == CloneFieldBehaviour.ClearReference)
               {
                  if (fi.Key.FieldType.IsValueType && fi.Key.FieldType.GetGenericTypeDefinition() != typeof(Nullable<>))
                  {
                     throw new ArgumentException("Cannot use the CloneFieldBehaviour.ClearReference on the field " + fi.Key.ToString() + " on type " + fi.Key.ReflectedType.ToString() + ". CloneFieldBehaviour.ClearReference can only be used on reference types.");
                  }
                  
                  ilGen.Emit(OpCodes.Ldloc_2); // Put the clone object reference on the stack. ( this is for the Stfld to store the clone value, to avoid the use of a local variable )
                  ilGen.Emit(OpCodes.Ldnull);  // Clear the reference by setting it to null.
                  if (fi.Key.FieldType.GetGenericTypeDefinition() == typeof(Nullable<>))
                  {
                     ilGen.Emit(OpCodes.Castclass, fi.Key.FieldType);
                  }
                  ilGen.Emit(OpCodes.Stfld, fi.Key); // Save the result back in the field
               }
               else if (fi.Value == CloneFieldBehaviour.UseCloneIfAlreadyCloned && !fi.Key.FieldType.IsValueType)
               {
                  // Check if we already have a clone of the object instance...
                  ilGen.Emit(OpCodes.Ldloc_2); // Put the clone object reference on the stack. ( this is for the Stfld to store the clone value, to avoid the use of a local variable )
                  ilGen.Emit(OpCodes.Ldarg_1);   // Put the clone context on the stack
                  ilGen.Emit(OpCodes.Ldsfld, propCloneType.GetField("TypeIndex", BindingFlags.Public | BindingFlags.Static)); // Put the type index on the stack.
                  ilGen.Emit(OpCodes.Ldloc_2); // Put the clone object reference on the stack.
                  ilGen.Emit(OpCodes.Ldfld, fi.Key); // Get the field value
                  ilGen.Emit(OpCodes.Call, s_cloningcontextGetExistingClone);
                  ilGen.Emit(OpCodes.Stfld, fi.Key); // Save the result back in the field

                  var labelFieldObjectAlreadyCloned = ilGen.DefineLabel();
                  ilGen.Emit(OpCodes.Ldloc_2);   // Put the cloned object on the stack.
                  ilGen.Emit(OpCodes.Ldfld, fi.Key); // Get the field value
                  ilGen.Emit(OpCodes.Brtrue_S, labelFieldObjectAlreadyCloned);
                  // The field object was not already cloned. Get the original reference.
                  ilGen.Emit(OpCodes.Ldloc_2); // Put the clone object reference on the stack.
                  ilGen.Emit(OpCodes.Ldloc_1); // Put the original object reference on the stack.
                  ilGen.Emit(OpCodes.Ldfld, fi.Key); // Get the field value from the original.
                  ilGen.Emit(OpCodes.Stfld, fi.Key); // Save the result back in the field of the clone.

                  ilGen.MarkLabel(labelFieldObjectAlreadyCloned);
               }
               else if (fi.Value == CloneFieldBehaviour.ShallowClone)
               {
                  if (!fi.Key.FieldType.IsValueType) // Value types is already shallow cloned, so only other types need an extra operation for shallow cloning.
                  {
                     MethodInfo miShallowClone = fi.Key.FieldType.IsArray ?
                        s_arrayClone : // Array.Clone
                        s_objectMemberwiseClone ;
                     var lblNullTest_ShallowCopyField = ilGen.DefineLabel();

                     ilGen.Emit(OpCodes.Ldloc_2); // Put the clone object reference on the stack.
                     ilGen.Emit(OpCodes.Ldfld, fi.Key); // Put the field value on the stack
                     ilGen.Emit(OpCodes.Brfalse_S, lblNullTest_ShallowCopyField);

                     ilGen.Emit(OpCodes.Ldloc_2); // Put the clone object reference on the stack. ( this is for the Stfld to store the clone value, to avoid the use of a local variable )
                     ilGen.Emit(OpCodes.Dup); //ilGen.Emit(OpCodes.Ldloc_2); // Put the clone object reference on the stack.
                     ilGen.Emit(OpCodes.Ldfld, fi.Key); // Put the field value on the stack
                     ilGen.Emit(OpCodes.Call, miShallowClone);  // Call the clone object method for the property value
                     ilGen.Emit(OpCodes.Stfld, fi.Key); // Save the result back in the field

                     ilGen.MarkLabel(lblNullTest_ShallowCopyField);
                  }
               }
               else if (fi.Key.FieldType.IsInterface || fi.Key.FieldType == typeof(object))
               {
                  // Need to clone the actual object and not the interface type.
                  ilGen.Emit(OpCodes.Ldloc_2); // Put the clone on the stack. ( this is for the Stfld to store the clone value, to avoid the use of a local variable )
                  ilGen.Emit(OpCodes.Dup); //ilGen.Emit(OpCodes.Ldloc_2); // Put the clone on the stack.
                  ilGen.Emit(OpCodes.Ldfld, fi.Key); // Put the field value on the stack
                  ilGen.Emit(OpCodes.Ldarg_1);   // Put the clone context on the stack
                  ilGen.Emit(OpCodes.Call, s_miDeepCloneObject);  // Call the clone object method for the property value
                  ilGen.Emit(OpCodes.Stfld, fi.Key); // Save the result back in the field
               }
               else if (fi.Key.FieldType == metadata.ObjectType)
               {
                  // The property has the same type at the one being cloned. Should make a call to this function.
                  ilGen.Emit(OpCodes.Ldloc_2); // Put the clone object reference on the stack. ( this is for the Stfld to store the clone value, to avoid the use of a local variable )
                  ilGen.Emit(OpCodes.Dup); //ilGen.Emit(OpCodes.Ldloc_2); // Put the clone object reference on the stack.
                  ilGen.Emit(OpCodes.Ldfld, fi.Key);  // Put the field value on the stack
                  ilGen.Emit(OpCodes.Ldarg_1);        // Put the clone context on the stack
                  ilGen.Emit(OpCodes.Call, deepCloneDynamicMethod);  // Call the clone metod for the property value
                  ilGen.Emit(OpCodes.Stfld, fi.Key); // Save the result back in the field
               }
               else
               {
                  MethodInfo miCloneTypedObjectInstance = propCloneType.GetField("DeepCloneDynamicMethod", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static).GetValue(null) as MethodInfo;
                  if (null == miCloneTypedObjectInstance)
                  {
                     miCloneTypedObjectInstance = propCloneType.GetMethod("DeepCloneTypedObjectInstance", new Type[] { fi.Key.FieldType, typeof(CloningContext) });
                  }
                  // Put the object beeing cloned twice on the stack.
                  // First for getting the field value and second for putting the cloned value back in the field. (optimization for avoiding having to have a local variable)
                  ilGen.Emit(OpCodes.Ldloc_2); // Put the clone object reference on the stack. ( this is for the Stfld to store the clone value, to avoid the use of a local variable )
                  ilGen.Emit(OpCodes.Dup); //ilGen.Emit(OpCodes.Ldloc_2); // Put the clone object reference on the stack.
                  ilGen.Emit(OpCodes.Ldfld, fi.Key); // Put the field value on the stack
                  ilGen.Emit(OpCodes.Ldarg_1);   // Put the clone context on the stack
                  ilGen.Emit(OpCodes.Call, miCloneTypedObjectInstance);  // Call the clone metod for the property value
                  ilGen.Emit(OpCodes.Stfld, fi.Key); // Save the result back in the field
               }
            }
         }


         internal static DynamicMethod CreateILArrayCloneMethod(CloneTypeMetadata metadata)
         {
            Type elementType = metadata.ObjectType.GetElementType();
            CloneTypeMetadata elementMetadata = CloneTypeMetadata.GetMetadata(elementType);
            Type memberOfType = (!elementType.IsArray && elementType.IsClass) || elementType.IsValueType ? elementType : typeof(Array);
            DynamicMethod deepCloneDynamicMethod =
               new DynamicMethod(metadata.ObjectType.FullName + "_ILArrayClone",
                                 metadata.ObjectType, // Returns cloned object of type T.
                                 new Type[] { metadata.ObjectType, // Object to clone 
                                              typeof(CloningContext) }, // Existing clones dictionary
                                 memberOfType,
                                 true);
            ILGenerator ilGen = deepCloneDynamicMethod.GetILGenerator(120);

            if (metadata.ClassBehaviour == CloneTypeBehaviour.KeepOriginal)
            {
               ilGen.Emit(OpCodes.Ldarg_0);  // Put the original on the stack.
               ilGen.Emit(OpCodes.Ret);    // and return.
               return deepCloneDynamicMethod;
            }
            else if (metadata.ClassBehaviour == CloneTypeBehaviour.ShallowClone)
            {
               ilGen.Emit(OpCodes.Ldarg_0);  // Put the original on the stack.
               ilGen.Emit(OpCodes.Call, s_arrayClone); // Make a shallow copy
               ilGen.Emit(OpCodes.Ret);    // and return.
               return deepCloneDynamicMethod;
            }

            var lblNullTest1 = ilGen.DefineLabel();

            ilGen.Emit(OpCodes.Ldarg_0);  // Put the original on the stack.
            ilGen.Emit(OpCodes.Brtrue_S, lblNullTest1); // Check if null...
            // The value was null.
            ilGen.Emit(OpCodes.Ldnull); // Put null on the stack
            ilGen.Emit(OpCodes.Ret);    // and return.

            // We have a non null value
            ilGen.MarkLabel(lblNullTest1);

            var cloneElementBehaviour = elementMetadata.ClassBehaviour;
            if (CloneTypeBehaviour.KeepOriginal == cloneElementBehaviour || CloneTypeBehaviour.ShallowClone == cloneElementBehaviour)
            {
               // This is an array of an immutable type or the type is configured to only be shallow copied.
               ilGen.Emit(OpCodes.Ldarg_0);
               ilGen.Emit(OpCodes.Call, s_arrayClone);
            }
            else if (metadata.ObjectType.GetArrayRank() == 1 && elementType.MakeArrayType() == metadata.ObjectType) // Is it a simple one dimmentional zero index based array?
            {
               if (elementType.IsValueType)
               {
                  var fields = elementMetadata.FieldBehaviours.Where(kv => kv.Value != CloneFieldBehaviour.KeepOriginal);
                  var loc_array = ilGen.DeclareLocal(metadata.ObjectType);
                  var loc_originalRef = ilGen.DeclareLocal(elementType.MakeByRefType());
                  var loc_cloneRef = ilGen.DeclareLocal(elementType.MakeByRefType());
                  var loc_idx = ilGen.DeclareLocal(typeof(int));

                  var lbl_loopStart = ilGen.DefineLabel();
                  var lbl_loopEndTest = ilGen.DefineLabel();

                  // Shallow copy + EmitMemberFieldsCloneCode for value types
                  ilGen.Emit(OpCodes.Ldarg_0);
                  ilGen.Emit(OpCodes.Call, s_arrayClone);
                  if (fields.Any())
                  {
                     ilGen.Emit(OpCodes.Stloc_0); // Save the cloned array in local variable 0.

                     ilGen.Emit(OpCodes.Ldc_I4_0); // Initialize loop counter to zero...
                     ilGen.Emit(OpCodes.Stloc_3);
                     ilGen.Emit(OpCodes.Br, lbl_loopEndTest); // Jump to the for loop end test

                     ilGen.MarkLabel(lbl_loopStart);
                     // Put the reference to the new cloned element in local variable 2
                     ilGen.Emit(OpCodes.Ldloc_0);
                     ilGen.Emit(OpCodes.Ldloc_3);
                     ilGen.Emit(OpCodes.Ldelema, elementType);
                     ilGen.Emit(OpCodes.Stloc_2);
                     // Put a reference to the original element in local variable 1
                     ilGen.Emit(OpCodes.Ldarg_0);
                     ilGen.Emit(OpCodes.Ldloc_3);
                     ilGen.Emit(OpCodes.Ldelem_Ref);
                     ilGen.Emit(OpCodes.Stloc_1);
                     // Emit code to handle mutable members of the element
                     EmitMemberFieldsCloneCode(ilGen, deepCloneDynamicMethod, fields, metadata);
                     ilGen.Emit(OpCodes.Ldloc_3);
                     ilGen.Emit(OpCodes.Ldc_I4_1);
                     ilGen.Emit(OpCodes.Add);
                     ilGen.Emit(OpCodes.Stloc_3);

                     ilGen.MarkLabel(lbl_loopEndTest);
                     ilGen.Emit(OpCodes.Ldloc_3);
                     ilGen.Emit(OpCodes.Ldloc_0);
                     ilGen.Emit(OpCodes.Ldlen);
                     ilGen.Emit(OpCodes.Conv_I4);
                     ilGen.Emit(OpCodes.Blt, lbl_loopStart);

                     ilGen.Emit(OpCodes.Ldloc_0); // Put the cloned array on the stack
                  }
               }
               else
               {
                  var loc_array = ilGen.DeclareLocal(metadata.ObjectType);
                  var loc_idx = ilGen.DeclareLocal(typeof(int));

                  var lbl_loopStart = ilGen.DefineLabel();
                  var lbl_loopEndTest = ilGen.DefineLabel();

                  // Create a new array with the same type and length
                  ilGen.Emit(OpCodes.Ldarg_0);
                  ilGen.Emit(OpCodes.Ldlen);
                  ilGen.Emit(OpCodes.Newarr, elementType);
                  ilGen.Emit(OpCodes.Stloc_0); // Save the new array in local variable 0.

                  // Clone each element.
                  MethodInfo miClone = elementMetadata.GetCloneFactoryType().GetField("DeepCloneDynamicMethod", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static).GetValue(null) as MethodInfo;
                  if (null == miClone)
                  {
                     miClone = elementMetadata.GetCloneFactoryType().GetMethod("DeepCloneTypedObjectInstance", new Type[] { elementType, typeof(CloningContext) });
                  }

                  ilGen.Emit(OpCodes.Ldc_I4_0); // Initialize loop counter to zero...
                  ilGen.Emit(OpCodes.Stloc_1);
                  ilGen.Emit(OpCodes.Br, lbl_loopEndTest); // Jump to the for loop end test

                  ilGen.MarkLabel(lbl_loopStart);
                  ilGen.Emit(OpCodes.Ldloc_0); // Put the new array on the stack
                  ilGen.Emit(OpCodes.Ldloc_1); // Get the current index
                  ilGen.Emit(OpCodes.Ldarg_0);  // Put the original array on the stack
                  ilGen.Emit(OpCodes.Ldloc_1);  // Get the current index
                  ilGen.Emit(OpCodes.Ldelem, elementType); // Get the element at the current index
                  ilGen.Emit(OpCodes.Ldarg_1); // Put the CloneContext on the stack
                  ilGen.Emit(OpCodes.Call, miClone); // Clone the element
                  ilGen.Emit(OpCodes.Stelem, elementType); // Store the cloned object in the new array

                  // Increment the index
                  ilGen.Emit(OpCodes.Ldloc_1);
                  ilGen.Emit(OpCodes.Ldc_I4_1);
                  ilGen.Emit(OpCodes.Add);
                  ilGen.Emit(OpCodes.Stloc_1);

                  ilGen.MarkLabel(lbl_loopEndTest);
                  ilGen.Emit(OpCodes.Ldloc_1);
                  ilGen.Emit(OpCodes.Ldloc_0);
                  ilGen.Emit(OpCodes.Ldlen);
                  ilGen.Emit(OpCodes.Conv_I4);
                  ilGen.Emit(OpCodes.Blt, lbl_loopStart);

                  ilGen.Emit(OpCodes.Ldloc_0); // Put the cloned array on the stack
               }
            }
            else
            {
               ilGen.Emit(OpCodes.Ldarg_0); // Put the original array on the stack
               ilGen.Emit(OpCodes.Call, s_arrayClone); // Make a shallow copy of the array

               ilGen.Emit(OpCodes.Dup); // Put an extra copy of the cloned array pointer on the stack.
               ilGen.Emit(OpCodes.Ldarg_1); // Put the CloneContext on the stack
               ilGen.Emit(OpCodes.Call, s_miInPlaceCloneArrayMembers); // Make a deep copy of each element in the shallow cloned array.
            }
            ilGen.Emit(OpCodes.Ret);

            return deepCloneDynamicMethod;
         }


         internal static DynamicMethod CreateILUnknownObjectCloneMethod(CloneTypeMetadata metadata)
         {
            DynamicMethod DeepCloneDynamicMethod =
               new DynamicMethod(metadata.ObjectType.FullName + "_ILUnknownObjectClone",
                                 metadata.ObjectType, // Returns cloned object of type T.
                                 new Type[] { metadata.ObjectType, // Object to clone 
                                              typeof(CloningContext) }, // Existing clones dictionary
                                 typeof(object),
                                 true);
            ILGenerator ilGen = DeepCloneDynamicMethod.GetILGenerator(12);

            ilGen.Emit(OpCodes.Ldarg_0);  // Put the original on the stack.
            ilGen.Emit(OpCodes.Ldarg_1);  // Put the existing clones dictionary on the stack
            ilGen.Emit(OpCodes.Call, s_miDeepCloneObject);  // Call the clone method for any object value
            ilGen.Emit(OpCodes.Ret);

            return DeepCloneDynamicMethod;
         }

      }


      /// <summary>
      /// Class containing the context in which a cloning operation runs.
      /// </summary>
      private class CloningContext
      {
         public readonly CloneContextEntry[] ObjectReferences;

         public CloningContext()
         {
            int capacity = CloneFactoryHelper.ObjectTypeIndexCounter;
            if (7 > capacity)
               capacity = 7;
            else if (17 > capacity)
               capacity = 17;
            else if (29 > capacity)
               capacity = 29;
            else if (37 > capacity)
               capacity = 37;
            else if (47 > capacity)
               capacity = 47;
            else if (59 > capacity)
               capacity = 59;
            else
               capacity = 71;

            ObjectReferences = new CloneContextEntry[capacity];
         }

         // Gets an existing clone of an object instance, if already cloned.
         // Important to only clone each object instance once, to handle circle references correctly.
         public object GetExistingClone(int typeIndex, object obj)
         {
            typeIndex %= ObjectReferences.Length;
            if (object.ReferenceEquals(ObjectReferences[typeIndex].original, obj))
            {
               return ObjectReferences[typeIndex].clone;
            }
            var entry = ObjectReferences[typeIndex].next;
            while (null != entry)
            {
               if (object.ReferenceEquals(entry.entry.original, obj))
               {
                  return entry.entry.clone;
               }
               entry = entry.entry.next;
            }
            return null;
         }

         // Adds an object clone to the dictionary of cloned objects.
         public void AddNewClone(int typeIndex, object obj, object clone)
         {
            typeIndex %= ObjectReferences.Length;
            if (null == ObjectReferences[typeIndex].original)
            {
               ObjectReferences[typeIndex].original = obj;
               ObjectReferences[typeIndex].clone = clone;
               return;
            }
            if (null == ObjectReferences[typeIndex].next)
            {
               ObjectReferences[typeIndex].next = new CloneContextEntryExtension(obj, clone);
               return;
            }
            var next = ObjectReferences[typeIndex].next;
            while (null != next.entry.next)
            {
               next = next.entry.next;
            }
            next.entry.next = new CloneContextEntryExtension(obj, clone);
         }

         public T Convert<T>(T obj)
         {
            return CloneFactoryHelper<T>.MemberwiseDeepCloneDelegate(obj, this);
         }
      }

      /// <summary>
      /// An object clone entry in the ObjectCloneContext
      /// </summary>
      private struct CloneContextEntry
      {
         public object original;
         public object clone;
         public CloneContextEntryExtension next;

         public override string ToString()
         {
            return string.Format("{0X} - {1X}", original, clone);
         }
      }

      private class CloneContextEntryExtension
      {
         public CloneContextEntryExtension(object obj, object clone)
         {
            entry.original = obj;
            entry.clone = clone;
         }
         public CloneContextEntry entry;
      }


      private abstract class CloneTypeMetadata
      {

         public static CloneTypeMetadata GetMetadata(Type type)
         {
            return (CloneTypeMetadata)Activator.CreateInstance(typeof(CloneTypeMetadata<>).MakeGenericType(type));
         }

         public abstract Type ObjectType { get; }
         public abstract int TypeIndex { get; }
         public abstract CloneTypeBehaviour ClassBehaviour { get; }
         public abstract IEnumerable<KeyValuePair<FieldInfo, CloneFieldBehaviour>> FieldBehaviours { get; }
         public abstract Type GetCloneFactoryType();
      }

      /// <summary>
      /// Metadata for the type being cloned.
      /// </summary>
      /// <typeparam name="T">The type to set matadata for cloning.</typeparam>
      private class CloneTypeMetadata<T> : CloneTypeMetadata
      {
         private static object s_syncObject = new object();
         private static bool s_isClosed;
         private static CloneTypeBehaviour s_classBehaviour;

         public static CloneTypeBehaviour GetClassBehaviour()
         {
            return CloneTypeMetadata<T>.s_classBehaviour;
         }

         private static Dictionary<FieldInfo, CloneFieldBehaviour> s_fieldBehaviours;

         static CloneTypeMetadata()
         {
            s_isClosed = false;
            s_classBehaviour = GetTypeDefaultBehaviour(typeof(T));
            s_fieldBehaviours = new Dictionary<FieldInfo, CloneFieldBehaviour>();
            SetFieldsDefaultBehaviour();
         }

         private static CloneTypeBehaviour GetTypeDefaultBehaviour(Type type)
         {
            if (type.IsPrimitive || // Primitives are by nature immutable.
                type.IsEnum)        // Enums are stored as an integral primitive and are immutable
            {
               return CloneTypeBehaviour.KeepOriginal; // Struct copy
            }
            else if(type == typeof(string))
            {
               return CloneTypeBehaviour.KeepOriginal;
            }
            else if (type == typeof(DateTime) || type == typeof(Nullable<DateTime>))
            {
               return CloneTypeBehaviour.KeepOriginal;
            }
            else if (type == typeof(TimeSpan) || type == typeof(Nullable<TimeSpan>))
            {
               return CloneTypeBehaviour.KeepOriginal;
            }
            else if (type == typeof(Decimal) || type == typeof(Nullable<Decimal>))
            {
               return CloneTypeBehaviour.KeepOriginal;
            }
            else if (type == typeof(Guid) || type == typeof(Nullable<Guid>))
            {
               return CloneTypeBehaviour.KeepOriginal;
            }
            else if (type == typeof(Type))
            {
               return CloneTypeBehaviour.KeepOriginal;
            }
            else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) // Is it a Nullable of an immutable type?
            {
               return GetTypeDefaultBehaviour(type.GetGenericArguments()[0]);
            }

            var attr = type.GetCustomAttributes(typeof(CloneTypeBehaviourAttribute), false).FirstOrDefault() as CloneTypeBehaviourAttribute;
            if(null == attr)
            {
               attr = type.GetCustomAttributes(typeof(CloneTypeBehaviourAttribute), true).FirstOrDefault() as CloneTypeBehaviourAttribute;
            }
            if(null != attr)
            {
               return attr.CloneBehaviour;
            }

            return CloneTypeBehaviour.DeepClone; // Default behaviour
         }

         private static void SetFieldsDefaultBehaviour()
         {
            Type t = typeof(T);
            List<FieldInfo> fields = new List<FieldInfo>();
            while (null != t)
            {
               fields.AddRange(t.GetFields(BindingFlags.Instance     | // Only instance fields
                                           BindingFlags.DeclaredOnly | // Only declared directly on this class
                                           BindingFlags.NonPublic    | // Both non-public
                                           BindingFlags.Public));      // and public fields.
               t = t.BaseType;
            }

            foreach (FieldInfo fi in fields)
            {
               CloneFieldBehaviour defaultBehaviour = CloneFieldBehaviour.DefaultCloneBehaviour;

               // First we check if the clone behaviour has been specified for the field with the CloneFieldBehaviourAttribute.
               var attr = fi.GetCustomAttributes(typeof(CloneFieldBehaviourAttribute), true).FirstOrDefault() as CloneFieldBehaviourAttribute;
               if (null != attr)
               {
                  if (attr.CloneBehaviour == CloneFieldBehaviour.ClearReference && fi.FieldType.IsValueType && fi.FieldType.GetGenericTypeDefinition() != typeof(Nullable<>))
                  {
                     throw new ArgumentException("Cannot use the CloneFieldBehaviour.ClearReference on the field " + fi.ToString() + " on type " + fi.ReflectedType.ToString() + ". CloneFieldBehaviour.ClearReference can only be used on reference types.");
                  }

                  defaultBehaviour = attr.CloneBehaviour;
               }

               if (CloneFieldBehaviour.DefaultCloneBehaviour == defaultBehaviour)
               {
                  if (fi.FieldType.IsPrimitive ||  // Primitives are by nature immutable.
                      fi.FieldType.IsEnum)         // Enums are stored as an integral primitive and are immutable
                  {
                     defaultBehaviour = CloneFieldBehaviour.KeepOriginal;
                  }
                  else if (fi.FieldType == typeof(string))
                  {
                     defaultBehaviour = CloneFieldBehaviour.KeepOriginal; // Strings are immutable, so keep references by default.
                  }
                  else if (fi.FieldType.IsGenericType && fi.FieldType.GetGenericTypeDefinition() == typeof(Nullable<>)) // Is it a Nullable of an immutable type?
                  {
                     Type type2 = fi.FieldType.GetGenericArguments()[0];
                     if (type2.IsPrimitive ||  // Primitives are by nature immutable.
                         type2.IsEnum)         // Enums are stored as an integral primitive and are immutable
                     {
                        defaultBehaviour = CloneFieldBehaviour.KeepOriginal;
                     }
                  }
                  else if (typeof(MulticastDelegate).IsAssignableFrom(fi.FieldType))
                  {
                     // In most cases an event subscription, which should not be cloned.
                     defaultBehaviour = CloneFieldBehaviour.ClearReference;
                  }
               }
               s_fieldBehaviours[fi] = defaultBehaviour;
            }
         }

         internal static void SetTypeCloneBehaviour(CloneTypeBehaviour behaviour)
         {
            lock(s_syncObject)
            {
               if(s_isClosed)
               {
                  throw new InvalidOperationException("The metadata cannot be changed for a type after first cloning operation.");
               }
               s_classBehaviour = behaviour;
            }
         }

         internal static void SetFieldCloneBehaviour(FieldInfo field, CloneFieldBehaviour behaviour)
         {
            lock(s_syncObject)
            {
               if (s_isClosed)
               {
                  throw new InvalidOperationException("The metadata cannot be changed for a type after first cloning operation.");
               }
               if (behaviour == CloneFieldBehaviour.ClearReference && field.FieldType.IsValueType && field.FieldType.GetGenericTypeDefinition() != typeof(Nullable<>))
               {
                  throw new ArgumentException("Cannot use the CloneFieldBehaviour.ClearReference on the field " + field.ToString() + " on type " + field.ReflectedType.ToString() + ". CloneFieldBehaviour.ClearReference can only be used on reference types.");
               }
               if(field.ReflectedType != typeof(T))
               {
                  throw new ArgumentException("The field " + field.ToString() + " is not a member of type " + typeof(T).ToString() + ".");
               }

               s_fieldBehaviours[field] = behaviour;
            }
         }

         internal static void CloseMetadata()
         {
            if (s_isClosed)
            {
               throw new InvalidOperationException("The metadata is already closed.");
            }
            s_isClosed = true;
         }

         public static IEnumerable<KeyValuePair<FieldInfo, CloneFieldBehaviour>> GetFieldBehaviours()
         {
            return s_fieldBehaviours as IEnumerable<KeyValuePair<FieldInfo, CloneFieldBehaviour>>;
         }


         public override Type ObjectType
         {
            get { return typeof(T); }
         }

         public override int TypeIndex
         {
            get { return CloneFactoryHelper<T>.TypeIndex; }
         }

         public override CloneTypeBehaviour ClassBehaviour
         {
            get { return CloneTypeMetadata<T>.GetClassBehaviour(); }
         }

         public override IEnumerable<KeyValuePair<FieldInfo, CloneFieldBehaviour>> FieldBehaviours
         {
            get { return GetFieldBehaviours(); }
         }

         public override Type GetCloneFactoryType()
         {
            return typeof(CloneFactoryHelper<T>);
         }
      }
   }


   /// <summary>
   /// Specifies the cloning behaviour of a class, struct or a field.
   /// </summary>
   public enum CloneTypeBehaviour
   {
      ///<summary>Always make a deep clone. Default behaviour.</summary>
      DeepClone = 0,
      ///<summary>Always make a shallow clone.</summary>
      ShallowClone = 1,
      ///<summary>No cloning is done. The original reference to this object is kept on other objects when they are cloned.</summary>
      KeepOriginal = 2,
   }

   /// <summary>
   /// Attribute controling the cloning behaviour on a class or struct.
   /// </summary>
   [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
   public class CloneTypeBehaviourAttribute : Attribute
   {
      public readonly CloneTypeBehaviour CloneBehaviour;

      public CloneTypeBehaviourAttribute(CloneTypeBehaviour behaviour)
      {
         CloneBehaviour = behaviour;
      }
   }

   /// <summary>
   /// Specifies the cloning behaviour of a class, struct or a field.
   /// </summary>
   public enum CloneFieldBehaviour
   {
      ///<summary>Use the default clone behaviour of the type of the field. This is the default behaviour.</summary>
      DefaultCloneBehaviour = 0,

      ///<summary>Always make a deep clone.</summary>
      DeepClone = 1,

      ///<summary>Always make a shallow clone.</summary>
      ShallowClone = 2,

      ///<summary>Only use a clone if the object is already cloned at a higher level. Otherwise keep the reference.</summary>
      UseCloneIfAlreadyCloned = 3,

      ///<summary>No cloning is done. The original reference or value is always kept.</summary>
      KeepOriginal = 4,

      ///<summary>References to the object type is cleared(set to null).</summary>
      ClearReference = 5
   }

   /// <summary>
   /// Attribute controling the cloning behaviour of a field.
   /// </summary>
   [AttributeUsage(AttributeTargets.Field)]
   public class CloneFieldBehaviourAttribute : Attribute
   {
      public readonly CloneFieldBehaviour CloneBehaviour;

      public CloneFieldBehaviourAttribute(CloneFieldBehaviour behaviour)
      {
         CloneBehaviour = behaviour;
      }
   }

}